package cc.owoo.godpen.analysis.html;

import cc.owoo.godpen.structure.CustomLinkedList;

import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;

/**
 * HTML解析器
 * Created by 摸鱼
 * 2022/05/23 下午 01:15
 */
public class HtmlAnalysis {
    static final char[] ANNOTATION_LEFT = "--".toCharArray();
    static final char[] ANNOTATION_RIGHT = "-->".toCharArray();
    private final CustomLinkedList<AnalysisContext> context = new CustomLinkedList<>();// 上下文
    private Element parent;// 当前父元素
    private StringBuilder text = new StringBuilder();// 解析的文本
    private final Document document;// dom接收器
    private final HtmlStringAnalysis analysis;// 需要解析的字符串

    private boolean isAppendSpace;// 在拼接下一个字符的时候，中间是否需要添加空格
    private boolean isOriginal;// 是否开启原文解析（不删除多余空格）
    private String isText;// 文本解析，不解析标签元素，除非遇到特点的标签名称
    private boolean isHead = true;// 当前上下文是否在head中
    private boolean isHeadChild = false;// 是否在构建head的子元素

    public HtmlAnalysis(Document document, HtmlStringAnalysis string) {
        this.parent = document.body;
        this.document = document;
        this.analysis = string;
        context.addLast(new AnalysisContext(parent));
    }

    /**
     * 开始解析HTML
     */
    public void analysis() {
        while (analysis.hashNext()) {
            con:
            if (analysis.next() == '<') {
                if (!analysisElement())
                    break con;
                if (isText != null) {
                    analysisText();
                    isText = null;
                }
                continue;
            }
            analysis.reset();
            if (!analysis.hashNext())
                break;
            String s = analysis.nextString('<');
            analysis.reset();
            if (isOriginal)
                text.append(s);
            else {
                for (char c : s.toCharArray()) {
                    if (c <= 32)
                        isAppendSpace = true;
                    else
                        appendText(String.valueOf(c));
                }
            }
        }
        if (isAppendSpace)
            text.append(' ');
        if (!text.isEmpty())
            parent.appendChildNode(new Text(text.toString()));
        context.clear();
    }

    /**
     * 解析元素
     */
    private boolean analysisElement() {
        if (!analysis.hashNext())
            return false;
        char c = analysis.next();
        if (c <= 32 || !analysis.hashNext() || c == '>') {
            appendText("<");
            return false;
        }
        if (c == '!') {
            if (!analysis.hashNext())
                return false;
            analysis.next();
            if (analysis.startsWith(ANNOTATION_LEFT)) {
                analysis.indexAdd(ANNOTATION_LEFT.length);
                analysisAnnotation();
            } else
                analysisProperty();
            return true;
        }
        if (c == '/') {
            analysisElementEnd();
            return true;
        }
        int before = analysis.getIndex();
        if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
            appendText("<");
            return false;
        }
        while (analysis.hashNext()) {
            c = analysis.next();
            if (c <= 32 || c == '/' || c == '>')
                break;
        }
        Element element = new Element(analysis.substring(before).toLowerCase());
        if (c != '>') {
            LinkedHashMap<String, String> attribute = new LinkedHashMap<>();
            analysisAttribute(attribute);
            attribute.forEach((key, value) -> element.setAttribute(key, value == null ? "" : value));
        }
        pushElement(element);
        return true;
    }

    /**
     * 解析尾部标签
     */
    private void analysisElementEnd() {
        int before = analysis.getNextIndex();
        char c = analysis.next();
        if (!analysis.hashNext())
            return;
        if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
            analysis.nextIndexOf('>');
            return;
        }
        while (analysis.hashNext()) {
            c = analysis.next();
            if (c <= 32 || c == '/' || c == '>')
                break;
        }
        String tagName = analysis.substring(before).toLowerCase();
        if (c != '>')
            analysis.nextIndexOf('>');
        unshiftElement(tagName);
    }


    /**
     * 解析文本
     */
    private void analysisText() {
        StringBuilder text = new StringBuilder();
        char[] matching = ("</" + isText).toCharArray();
        StringBuilder cache = new StringBuilder();
        boolean isWaitEnd = false;
        boolean isInterval = false;
        boolean isSpace = false;
        int index = 0;
        while (analysis.hashNext()) {
            char c = analysis.next();
            if (isWaitEnd) {
                if (c == '>')
                    break;
                cache.append(c);
                if (c <= 32 || c == '/') {
                    isInterval = true;
                    continue;
                }
                if (isInterval) {
                    analysis.reset();
                    break;
                }
                text.append(matching);
                text.append(cache);
                cache = new StringBuilder();
                isWaitEnd = false;
                continue;
            }
            if (c == matching[index]) {
                if (++index >= matching.length) {
                    isWaitEnd = true;
                    index = 0;
                }
                continue;
            }
            if (index != 0) {
                if (isSpace) {
                    text.append(' ');
                    isSpace = false;
                }
                text.append(matching, 0, index);
                if (c == matching[0]) {
                    index = 1;
                    continue;
                }
                index = 0;
            }
            if (isOriginal)
                text.append(c);
            else if (c <= 32)
                isSpace = true;
            else {
                if (isSpace) {
                    text.append(' ');
                    isSpace = false;
                }
                text.append(c);
            }
        }
        if (isSpace)
            text.append(' ');
        appendText(text.toString());
        appendTextNode();
        unshiftElement(isText);
    }

    /**
     * 解析注释
     */
    private void analysisAnnotation() {
        int before = analysis.getIndex();
        int after = analysis.nextIndexOf(ANNOTATION_RIGHT);
        appendTextNode();
        parent.appendChildNode(new Annotation(analysis.substring(before, after)));
    }

    /**
     * 解析属性类元素，例如DOCTYPE
     */
    private void analysisProperty() {
        LinkedList<String> list = new LinkedList<>();
        StringBuilder text = new StringBuilder();
        boolean isSpace = false;
        while (analysis.hashNext()) {
            char c = analysis.next();
            if (c == '>')
                break;
            if (c <= 32) {
                isSpace = true;
                continue;
            }
            if (isSpace) {
                if (text.length() > 0) {
                    list.add(text.toString());
                    text = new StringBuilder();
                }
                isSpace = false;
            }
            text.append(c);
        }
        if (text.length() > 0)
            list.add(text.toString());
        else if (list.size() == 0)
            return;
        document.setProperty(list.toArray(new String[0]));
    }

    /**
     * 解析元素的属性
     *
     * @param attribute 属性映射表
     */
    private void analysisAttribute(LinkedHashMap<String, String> attribute) {
        boolean isKeyValue = false;
        String key = null;
        int before = -1;
        while (analysis.hashNext()) {
            char c = analysis.next();
            if (c <= 32 || c == '>') {
                if (before == -1) {
                    if (c == '>')
                        break;
                    continue;
                }
                char[] value = analysis.substringArray(before, analysis.getIndex());
                if (key != null) {
                    attribute.putIfAbsent(key, new HtmlStringAnalysis(value).nextString(' '));
                    isKeyValue = false;
                    key = null;
                } else
                    key = new String(value);
                before = -1;
                if (c == '>')
                    break;
                continue;
            }
            if (isKeyValue) {
                if (before == -1) {
                    if (c == '\'' || c == '"') {
                        String value = analysis.nextString(c);
                        attribute.putIfAbsent(key, value);
                        key = null;
                        isKeyValue = false;
                        continue;
                    }
                    before = analysis.getIndex();
                }
                continue;
            }
            if (c == '/') {
                if (key != null) {
                    attribute.putIfAbsent(key, null);
                    key = null;
                } else if (before != -1) {
                    String value = analysis.substring(before);
                    attribute.putIfAbsent(value, null);
                    before = -1;
                }
            } else if (c == '=') {
                if (key != null) {
                    isKeyValue = true;
                    continue;
                }
                if (before == -1) {
                    before = analysis.getIndex();
                    continue;
                }
                isKeyValue = true;
                key = analysis.substring(before);
                before = -1;
            } else if (before == -1) {
                if (key != null) {
                    attribute.putIfAbsent(key, null);
                    key = null;
                }
                before = analysis.getIndex();
            }
        }
        if (key != null)
            attribute.putIfAbsent(key, null);
    }

    /**
     * 添加文字
     *
     * @param string 文字内容
     */
    private void appendText(String string) {
        if (isAppendSpace) {
            text.append(' ');
            isAppendSpace = false;
        }
        text.append(string);
    }

    /**
     * 将文本添加到元素列表中
     */
    private void appendTextNode() {
        if (text.length() > 0) {
            if (!isHeadChild)
                isHead = false;
            parent.appendChildNode(new Text(text.toString()));
        }
        if (isAppendSpace) {
            parent.appendChildNode(new Text(" "));
            isAppendSpace = false;
        }
        text = new StringBuilder();
    }

    /**
     * 添加元素
     *
     * @param element 元素对象
     */
    private void pushElement(Element element) {
        if (filtration(element))
            return;
        if (Element.ONLY.contains(element.getTagName()))
            unshiftElement(element.getTagName());
        if (Element.TEXT.contains(element.getTagName()))
            isText = element.getTagName();
        isOriginal = isOriginal || Element.ORIGINAL.contains(element.getTagName());
        appendTextNode();
        if (isHead && Element.QUOTE.contains(element.getTagName())) {
            document.head.appendChildNode(element);
            isHeadChild = true;
        } else {
            isHead = false;
            parent.appendChildNode(element);
        }
        if (!element.isSingle) {
            AnalysisContext analysisContext = new AnalysisContext(parent = element);
            analysisContext.isOriginal = isOriginal;
            context.addLast(analysisContext);
        }
    }

    private void unshiftElement(String tagName) {
        if (Element.IGNORE.contains(tagName) ||
                Element.INDIVIDUAL.contains(tagName))
            return;
        isHeadChild = false;
        var lastNode = context.getLastNode();
        while (lastNode != null && !context.isFirst(lastNode)) {
            if (tagName.equals(lastNode.value().getTagName())) {
                context.removeToLast(lastNode);
                appendTextNode();
                break;
            }
            lastNode = lastNode.prev();
        }
        if (lastNode == null)
            return;
        AnalysisContext analysisContext = context.getLastNode().value();
        isOriginal = analysisContext.isOriginal;
        parent = analysisContext.element;
    }


    /**
     * 过滤非dom元素的标签，例如html、body等
     *
     * @param element 需要过滤的元素
     * @return 是否被过滤，如果是则返回true，负责返回false
     */
    boolean filtration(Element element) {
        String tagName = element.getTagName();
        if (tagName.equals("html")) {
            String temp = element.getAttribute("lang");
            if (temp != null)
                document.lang = temp;
            return true;
        }
        Element documentElement = tagName.equals("head") ? document.head :
                tagName.equals("body") ? document.body : null;
        if (documentElement != null) {
            element.attribute.forEach(documentElement::setAttributeIfAbsent);
            return true;
        }
        return false;
    }

    /**
     * 解析上下文
     */
    private static class AnalysisContext {
        public Element element;// 当前上下文父元素
        public boolean isOriginal;// 是否开启原文解析

        public AnalysisContext(Element element) {
            this.element = element;
        }

        /**
         * 获取元素标签名称
         *
         * @return 标签名称
         */
        public String getTagName() {
            return element.getTagName();
        }
    }
}
